/**
 * Copyright (c) 2018-2023, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contains: 
 *
 * Change Logs:
 * Date           Author            Notes
 * 2023-07-13     Shiroko           Init
 */
#ifndef __DRIVERS_VIRTIO_VIRTIO_QUEUE_H__
#define __DRIVERS_VIRTIO_VIRTIO_QUEUE_H__

#include <nxos.h>

/*
 * Implement only split virtqueue
 * Note: Newer version of specification use alternated name for desc/avail/used:
 * Descriptor Table - Descriptor Area
 * Available Ring   - Driver Area
 * Used Ring        - Device Area
 * This Implement use legacy name since they're more widely known.
 */

#define VIRTIO_QUEUE_DESC_FLAG_NEXT     1   // marks a buffer as continuing via the next field
#define VIRTIO_QUEUE_DESC_FLAG_WRITE    2   // marks a buffer as write-only
#define VIRTIO_QUEUE_DESC_FLAG_INDIRECT 4   // the buffer contains a list of buffer descriptors

#define VIRTIO_QUEUE_USED_FLAG_NO_NOTIFY        1   // no notify when add a buffer (unreliable)
#define VIRTIO_QUEUE_AVAIL_FLAG_NO_INTERRUPT    1   // no interrupt then consume a buffer (unreliable)

// !Important: Currently we do not negotiate VIRTIO_F_EVENT_IDX

/* Virtqueue descriptors: 16 bytes.
 * These can chain together via "next". */
struct VirtioQueueDesc
{
    /* Address (guest-physical). */
    NX_U64 addr;
    /* Length. */
    NX_U32 len;
    /* The flags as indicated above. */
    NX_U16 flags;
    /* We chain unused descriptors via this, too */
    NX_U16 next;
};
typedef struct VirtioQueueDesc        VirtioQueueDesc;

struct VirtioQueueAvail
{
    NX_U16 flags;
    NX_U16 idx;
    NX_U16 ring[];
    /* Only if VIRTIO_F_EVENT_IDX: NX_U16 used_event; */
};
typedef struct VirtioQueueAvail       VirtioQueueAvail;

/* NX_U32 is used here for ids for padding reasons. */
struct VirtioQueueUsedElement
{
    /* Index of start of used descriptor chain. */
    NX_U32 id;
    /* Total length of the descriptor chain which was written to. */
    NX_U32 len;
};
typedef struct VirtioQueueUsedElement VirtioQueueUsedElement;

struct VirtioQueueUsed
{
    NX_U16                        flags;
    NX_U16                        idx;
    struct VirtioQueueUsedElement ring[];
    /* Only if VIRTIO_F_EVENT_IDX: NX_U16 avail_event; */
};
typedef struct VirtioQueueUsed        VirtioQueueUsed;

struct VirtioQueueTrace
{
    /*
     * For synchronous waiting
     */
    NX_Bool      waiting;    // Driver is expecting this idx
    NX_Bool      ok;         // Interrupt has been made
    NX_Semaphore lock;
    /*
     * For asynchronous completed notifying
     */
    NX_Bool      needNotify;
};
typedef struct VirtioQueueTrace       VirtioQueueTrace;

typedef struct VirtioQueue VirtioQueue;

typedef NX_Error(*VirtioQueueCompletedHandler)(NX_Device *device, VirtioQueue *queue, NX_U32 idx);

struct VirtioQueue
{
    NX_U16       queue_index;
    unsigned int ring_size; // be power of 2
    NX_U32       used_idx;

    VirtioQueueDesc  *desc; // alignment of 16
    VirtioQueueAvail *avail;// alignment of 2
    VirtioQueueUsed  *used; // alignment of 4

    NX_Bool          *free; // consider using bitset to reduce memory usage
    unsigned int     free_count;
    VirtioQueueTrace *trace;

    VirtioQueueCompletedHandler notifier;

    NX_Mutex freeLock;
    NX_Mutex availRingLock;
    NX_Spin  usedRingLock;
};

#define VIRTIO_QUEUE_DESC_TOTAL_SIZE(ring_size)     (sizeof(VirtioQueueDesc) * (ring_size))
#define VIRTIO_QUEUE_AVAIL_TOTAL_SIZE(ring_size)    (sizeof(VirtioQueueAvail) + (ring_size) * sizeof(NX_U16))
#define VIRTIO_QUEUE_USED_TOTAL_SIZE(ring_size)     (sizeof(VirtioQueueUsed) + (ring_size) * sizeof(VirtioQueueUsedElement))

#define VIRTIO_QUEUE_DESC_INVALID_ID (0xFFFFFFFF)

void VirtioQueueInit(NX_Device *device, NX_U16 queue_index, NX_U16 ring_size);
void VirtioQueueFree(NX_Device *device);
void VirtioQueueAlloc(NX_Device *device, int queue_num);
void VirtioQueueDestroy(NX_Device *device, NX_U16 queue_index);
VirtioQueue *VirtioQueueGet(NX_Device *device, NX_U16 queue_index);

NX_U32 VirtioQueueDescAlloc(VirtioQueue *queue);
NX_Error VirtioQueueDescAllocMany(VirtioQueue *queue, int num, NX_U32 *allocated_ids);
void VirtioQueueDescFree(VirtioQueue *queue, NX_U32 id);
void VirtioQueueDescFreeMany(VirtioQueue *queue, NX_U32 id);
VirtioQueueDesc *VirtioQueueDescGet(VirtioQueue *queue, NX_U32 id);

void VirtioQueueDescFill(VirtioQueue *queue, NX_U32 desc_id, NX_U64 addr, NX_U32 len, NX_U16 flags, NX_U16 next);
void VirtioQueuePutIntoAvailRing(VirtioQueue *queue, NX_U32 desc_id);

void VirtioQueueRegisterNotifier(VirtioQueue *queue, VirtioQueueCompletedHandler handler);
void VirtioQueueUnregisterNotifier(VirtioQueue *queue, VirtioQueueCompletedHandler handler);


#endif // __DRIVERS_VIRTIO_VIRTIO_QUEUE_H__
